<?php
/**
 * @package     Joomla.Plugin
 * @subpackage  System.Mootable
 *
 * @author      Roberto Segura <roberto@phproberto.com>
 * @copyright   (c) 2012 Roberto Segura. All Rights Reserved.
 * @license     GNU/GPL 2, http://www.gnu.org/licenses/gpl-2.0.htm
 */

defined('_JEXEC') or die;

JLoader::import('joomla.plugin.plugin');

/**
 * Main plugin class
 *
 * @package     Joomla.Plugin
 * @subpackage  System.Mootable
 * @since       2.5
 *
 */
class PlgSystemMootable extends JPlugin
{
	private $_params = null;

	// Plugin info constants
	const TYPE = 'system';

	const NAME = 'mootable';

	// Handy objects
	private $_app    = null;

	// Paths
	private $_pathPlugin = null;

	// Urls
	private $_urlPlugin = null;

	private $_urlJs     = null;

	private $_urlCss    = null;

	// Url parameters
	private $_layout = null;

	private $_option = null;

	private $_view   = null;

	private $_id     = null;

	// CSS & JS scripts calls
	private $_cssCalls 	= array();

	private $_jsCalls 	= array();

	// HTML positions to inject CSS & JS
	private $_htmlPositions = array(
			'headtop' => array( 'pattern' => "/(<head>)/isU",
								'replacement' => "$1\n\t##CONT##"),
			'headbottom' => array(  'pattern' => "/(<\/head>)/isU",
									'replacement' => "\n\t##CONT##\n$1"),
			'bodytop' => array( 'pattern' => "/(<body)(.*)(>)/isU",
								'replacement' => "$1$2$3\n\t##CONT##"),
			'bodybottom' => array(  'pattern' => "/(<\/body>)/isU",
									'replacement' => "\n\t##CONT##\n$1"),
			'belowtitle' => array(  'pattern' => "/(<\/title>)/isU",
									'replacement' => "$1\n\t##CONT##")
			);

	// Autogenerated with array_keys($this->_htmlPositions)
	private $_htmlPositionsAvailable = array();

	// Used to validate url
	private $_componentsEnabled = array('*');

	private $_viewsEnabled 		= array('*');

	// Configure applications where enable plugin
	private $_frontendEnabled 	= true;

	private $_backendEnabled 	= false;

	/**
	* Constructor
	*
	* @param   mixed  &$subject  Subject
	*/
	function __construct( &$subject )
	{
		parent::__construct($subject);

		// Required objects
		$this->_app = JFactory::getApplication();

		// Set the HTML available positions
		$this->_htmlPositionsAvailable = array_keys($this->_htmlPositions);

		// Load plugin parameters
		$this->_plugin = JPluginHelper::getPlugin(self::TYPE, self::NAME);
		$this->_params = new JRegistry($this->_plugin->params);

		// Init folder structure
		$this->_initFolders();

		// Load plugin language
		$this->loadLanguage('plg_' . self::TYPE . '_' . self::NAME, JPATH_ADMINISTRATOR);
	}

	/**
	 * This event is triggered immediately before pushing the document buffers into the template placeholders,
	 * retrieving data from the document and pushing it into the into the JResponse buffer.
	 * http://docs.joomla.org/Plugin/Events/System
	 *
	 * @return boolean
	 */
	function onBeforeRender()
	{
		// Validate view
		if (!$this->_validateUrl())
		{
			return true;
		}

		// Required objects
		$app        = JFactory::getApplication();
		$doc        = JFactory::getDocument();
		$pageParams = $app->getParams();

		// Check if we have to disable Mootools for this item
		$mootoolsMode = $pageParams->get('mootable', $this->_params->get('defaultMode', 0));
		$moreMode     = $pageParams->get('moreMode', $this->_params->get('defaultMoreMode', 0));

		$disableOnDebug = $this->_params->get('disableWhenDebug', 1);

		if (!$this->_isAutoEnabled() && 0 == $mootoolsMode)
		{
			// Function used to replace window.addEvent()
			$doc->addScriptDeclaration("function do_nothing() { return; }");

			// Disable mootools javascript
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools-core.js']);
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/core.js']);
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools-more.js']);
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/caption.js']);
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/modal.js']);
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools.js']);
			unset($doc->_scripts[JURI::root(true) . '/plugins/system/mtupgrade/mootools.js']);
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/validate.js']);

			// Disabled mootools javascript when debugging site
			if ($disableOnDebug)
			{
				unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools-core-uncompressed.js']);
				unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools-more-uncompressed.js']);
				unset($doc->_scripts[JURI::root(true) . '/media/system/js/core-uncompressed.js']);
				unset($doc->_scripts[JURI::root(true) . '/media/system/js/caption-uncompressed.js']);
			}

			// Disable css stylesheets
			unset($doc->_styleSheets[JURI::root(true) . '/media/system/css/modal.css']);
		}
		elseif (0 == $moreMode)
		{
			unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools-more.js']);

			if ($disableOnDebug)
			{
				unset($doc->_scripts[JURI::root(true) . '/media/system/js/mootools-more-uncompressed.js']);
			}
		}

		// Disable additional assets specified by the user
		$this->disablePageScripts();
		$this->disablePageStylesheets();

		return true;
	}

	/**
     * This event is triggered after pushing the document buffers into the template placeholders,
     * retrieving data from the document and pushing it into the into the JResponse buffer.
     * http://docs.joomla.org/Plugin/Events/System
     *
     * @return boolean
     */
	function onAfterRender()
	{
		// Validate view
		if (!$this->_validateUrl())
		{
			return true;
		}

		// Required objects
		$app        = JFactory::getApplication();
		$doc        = JFactory::getDocument();
		$pageParams = $app->getParams();

		// Check if we have to disable Mootools for this item
		$mode = $pageParams->get('mootable', $this->_params->get('defaultMode', 0));

		if (!$this->_isAutoEnabled() && !$mode)
		{
			// Get the generated content
			$body = JResponse::getBody();

			// Remove JCaption JS calls
			$pattern     = "/(new JCaption\()(.*)(\);)/isU";
			$replacement = '';
			$body        = preg_replace($pattern, $replacement, $body);

			// Null window.addEvent( calls
			$pattern = "/(window.addEvent\()(.*)(,)/isU";
			$body    = preg_replace($pattern, 'do_nothing(', $body);
			JResponse::setBody($body);
		}

		return true;
	}

	/**
	* Change forms before they are shown to the user
	*
	* @param   JForm  $form  JForm object
	* @param   array  $data  Data array
	*
	* @return boolean
	*/
	public function onContentPrepareForm($form, $data)
	{
		// Check we have a form
		if (!($form instanceof JForm))
		{
			$this->_subject->setError('JERROR_NOT_A_FORM');

			return false;
		}

		// Extra parameters for menu edit
		if ($form->getName() == 'com_menus.item')
		{
			$form->loadFile($this->_pathPlugin . '/forms/menuitem.xml');
		}

		return true;
	}

	/**
	 * Add a css file declaration
	 *
	 * @param   string  $cssUrl    url of the CSS file
	 * @param   string  $position  position where we are going to load JS
	 *
	 * @return none
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _addCssCall($cssUrl, $position = null)
	{
		// If position is not available we will try to load the url through $doc->addScript
		if (is_null($position) || !in_array($position, $this->_htmlPositionsAvailable))
		{
			$position = 'addstylesheet';
			$cssCall = $jsUrl;
		}
		else
		{
			$cssCall = '<link rel="stylesheet" type="text/css" href="' . $cssUrl . '" >';
		}

		// Initialize position
		if (!isset($this->_cssCalls[$position]))
		{
			$this->_cssCalls[$position] = array();
		}

		// Insert CSS call
		$this->_cssCalls[$position][] = $cssCall;
	}

	/**
	 * Add a JS script declaration
	 *
	 * @param   string  $jsUrl     url of the JS file or script content for type != url
	 * @param   string  $position  position where we are going to load JS
	 * @param   string  $type      url || script
	 *
	 * @return none
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 */
	private function _addJsCall($jsUrl, $position = null, $type = 'url')
	{
		// If position is not available we will try to load the url through $doc->addScript
		if (is_null($position) || !in_array($position, $this->_htmlPositionsAvailable))
		{
			$position = 'addscript';
			$jsCall = $jsUrl;
		}
		else
		{
			if ($type == 'url')
			{
				$jsCall = '<script src="' . $jsUrl . '" type="text/javascript"></script>';
			}
			else
			{
				$jsCall = '<script type="text/javascript">' . $jsUrl . '</script>';
			}
		}

		// Initialize position
		if (!isset($this->_jsCalls[$position]))
		{
			$this->_jsCalls[$position] = array();
		}

		// Insert JS call
		$this->_jsCalls[$position][] = $jsCall;
	}

	/**
	 * Disable the page scripts
	 *
	 * @return  void
	 */
	private function disablePageScripts()
	{
		$pageParams = JFactory::getApplication()->getParams();

		// Other scripts disabled
		$globalDisabled = str_replace("\n", ",", $this->_params->get('manualDisable', null));
		$menuDisabled   = str_replace("\n", ",", $pageParams->get('disabledScripts', null));

		$scriptsDisabled = array_unique(array_merge(explode(',', $globalDisabled), explode(',', $menuDisabled)));

		// Disable 3rd party extensions added by the user
		if (!empty($scriptsDisabled))
		{
			$doc = JFactory::getDocument();

			foreach ($scriptsDisabled as $script)
			{
				$script = trim($script);

				if (!empty($script))
				{
					$uri = JUri::getInstance();

					$relativePath   = trim(str_replace($uri->getPath(), '', JUri::root()), '/');
					$relativeScript = trim(str_replace($uri->getPath(), '', $script), '/');
					$relativeUrl    = str_replace($relativePath, '', $script);

					// Try to disable relative and full URLs
					unset($doc->_scripts[$script]);
					unset($doc->_scripts[$relativeUrl]);
					unset($doc->_scripts[JUri::root(true) . $script]);
					unset($doc->_scripts[$relativeScript]);
				}
			}
		}
	}

	/**
	 * Disable the page stylesheets
	 *
	 * @return  void
	 */
	private function disablePageStylesheets()
	{
		$pageParams = JFactory::getApplication()->getParams();

		// Other scripts disabled
		$globalDisabled = str_replace("\n", ",", $this->_params->get('disabledStylesheets', null));
		$menuDisabled   = str_replace("\n", ",", $pageParams->get('disabledStylesheets', null));

		$stylesheetsDisabled = array_unique(array_merge(explode(',', $globalDisabled), explode(',', $menuDisabled)));

		if (!empty($stylesheetsDisabled))
		{
			$doc = JFactory::getDocument();

			foreach ($stylesheetsDisabled as $stylesheet)
			{
				$stylesheet = trim($stylesheet);

				if (!empty($stylesheet))
				{
					$uri = JUri::getInstance();

					$relativePath   = trim(str_replace($uri->getPath(), '', JUri::root()), '/');
					$relativeStylesheet = trim(str_replace($uri->getPath(), '', $stylesheet), '/');
					$relativeUrl    = str_replace($relativePath, '', $stylesheet);

					// Try to disable relative and full URLs
					unset($doc->_styleSheets[$stylesheet]);
					unset($doc->_styleSheets[$relativeUrl]);
					unset($doc->_styleSheets[JUri::root(true) . $stylesheet]);
					unset($doc->_styleSheets[$relativeStylesheet]);
				}
			}
		}
	}

	/**
	 * initialize folder structure
	 *
	 * @return none
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _initFolders()
	{
		// Paths
		$this->_pathPlugin = JPATH_PLUGINS . '/' . self::TYPE . '/' . self::NAME;

		// Urls
		$this->_urlPlugin = JURI::root(true) . "/plugins/" . self::TYPE . "/" . self::NAME;
		$this->_urlJs     = $this->_urlPlugin . "/js";
		$this->_urlCss    = $this->_urlPlugin . "/css";
	}

	/**
	 * Detect if the user is editing an article
	 *
	 * @return boolean
	 *
	 * @author Roberto Segura
	 * @version 28/09/2012
	 *
	 */
	private function _isAutoEnabled()
	{
		$app    = JFactory::getApplication();
		$jinput = $app->input;
		$option = $jinput->get('option', null);
		$view 	= $jinput->get('view', null);
		$id     = $jinput->get('id', null);
		$layout = $jinput->get('layout', null);

		// Always enable mootools for given components
		if ($alwaysEnable = $this->_params->get('alwaysEnable', null))
		{
			// Allow ENTER separated and remove spaces
			$components = str_replace(array("\n", " "), array(",", ""), $alwaysEnable);
			$components = explode(',', $components);

			if (in_array($option, $components))
			{
				return true;
			}
		}

		// Allways enable for content edition
		$isContentEdit = $this->_params->get('contentEdition', 1);

		if ($app->isSite() &&  $isContentEdit && $option == 'com_content' && $view == 'form' && $layout == 'edit')
		{
			return true;
		}

		// Allways enable for frontend com_users (login, profile edit, etc.)
		$enableComUsers = $this->_params->get('enableComUsers', 1);

		if ($app->isSite() && $enableComUsers && $option == 'com_users')
		{
			return true;
		}

		return false;
	}

	/**
	 * Load / inject CSS
	 *
	 * @return string
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _loadCSS()
	{
		if (!empty($this->_cssCalls))
		{
			$body = JResponse::getBody();

			foreach ($this->_cssCalls as $position => $cssCalls)
			{
				if (!empty($cssCalls))
				{
					// If position is defined we append code (inject) to the desired position
					if ( in_array($position, $this->_htmlPositionsAvailable) )
					{
						// Generate the injected code
						$cssIncludes = implode("\n\t", $cssCalls);
						$pattern     = $this->_htmlPositions[$position]['pattern'];
						$replacement = str_replace('##CONT##', $cssIncludes, $this->_htmlPositions[$position]['replacement']);
						$body        = preg_replace($pattern, $replacement, $body);
					}
					else
					{
						$doc = JFactory::getDocument();

						foreach ($cssCalls as $cssUrl)
						{
							$doc->addStyleSheet($cssUrl);
						}
					}
				}
			}

			JResponse::setBody($body);

			return $body;
		}
	}

	/**
	 * Load / inject Javascript
	 *
	 * @return string
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _loadJS()
	{
		if (!empty($this->_jsCalls))
		{
			$body = JResponse::getBody();

			foreach ($this->_jsCalls as $position => $jsCalls)
			{
				if (!empty($jsCalls))
				{
					// If position is defined we append code (inject) to the desired position
					if ( in_array($position, $this->_htmlPositionsAvailable) )
					{
						// Generate the injected code
						$jsIncludes  = implode("\n\t", $jsCalls);
						$pattern     = $this->_htmlPositions[$position]['pattern'];
						$replacement = str_replace('##CONT##', $jsIncludes, $this->_htmlPositions[$position]['replacement']);
						$body        = preg_replace($pattern, $replacement, $body);
					}
					else
					{
						$doc = JFactory::getDocument();

						foreach ($jsCalls as $jsUrl)
						{
							$doc->addScript($jsUrl);
						}
					}
				}
			}

			JResponse::setBody($body);

			return $body;
		}
	}

	/**
	 * validate if the plugin is enabled for current application (frontend / backend)
	 *
	 * @return boolean
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _validateApplication()
	{
		if ( ($this->_app->isSite() && $this->_frontendEnabled) || ($this->_app->isAdmin() && $this->_backendEnabled) )
		{
			return true;
		}

		return false;
	}

	/**
	 * Validate option in url
	 *
	 * @return boolean
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _validateComponent()
	{
		if ( in_array('*', $this->_componentsEnabled) || in_array($this->_option, $this->_componentsEnabled) )
		{
			return true;
		}

		return false;
	}

	/**
	 * custom method for extra validations
	 *
	 * @return true
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _validateExtra()
	{
		return $this->_validateApplication();
	}

	/**
	 * plugin enabled for this url?
	 *
	 * @return boolean
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _validateUrl()
	{
		if ( $this->_validateComponent() && $this->_validateView())
		{
			if (method_exists($this, '_validateExtra'))
			{
				return $this->_validateExtra();
			}
			else
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * validate view parameter in url
	 *
	 * @return boolean
	 *
	 * @author Roberto Segura
	 * @version 14/08/2012
	 *
	 */
	private function _validateView()
	{
		if ( in_array('*', $this->_viewsEnabled) || in_array($this->_view, $this->_viewsEnabled))
		{
			return true;
		}

		return false;
	}
}
